package com.lazi.june.gateway.filter;

import com.lazi.june.common.base.RequestConst;
import com.lazi.june.gateway.config.GatewayProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.reactivestreams.Publisher;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Optional;


/**
 * 请求日志过滤器
 * <p>
 * 记录网关处理的每个请求的详细信息，包括请求方法、路径、响应状态码、处理耗时等。
 * 支持通过配置开关启用或禁用日志记录功能。
 * <p>
 * 日志内容：
 * - HTTP 方法（GET、POST、PUT、DELETE 等）
 * - 请求路径（不包含 Query 参数）
 * - 响应状态码（200、401、500 等）
 * - 请求处理耗时（毫秒）
 * - 追踪 ID（用于链路追踪和日志关联）
 * <p>
 * 日志格式示例：
 * <pre>
 * [GW] POST /api/users -> 200 (123ms) traceId=1234567890
 * [GW] GET /api/orders -> 401 (45ms) traceId=9876543210
 * </pre>
 * <p>
 * 追踪 ID 策略：
 * 1. 优先使用请求头中的 X-Trace-Id（支持分布式链路追踪）
 * 2. 如果请求头中没有，则使用 System.nanoTime() 生成唯一标识
 * <p>
 * 配置示例：
 * <pre>
 * june:
 *   gateway:
 *     log-enabled: true  # 启用请求日志
 * </pre>
 * <p>
 * 使用场景：
 * - 监控网关请求流量和性能
 * - 排查请求处理问题
 * - 分析 API 调用模式
 * - 分布式链路追踪
 *
 * @author huliqian
 * @since 2025/8/21
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RequestLoggingFilter implements GlobalFilter, Ordered {

    /**
     * 日志最大长度，避免日志过长
     */
    private static final int MAX_LOG_LENGTH = 500;

    /**
     * 网关配置属性
     */
    private final GatewayProperties props;

    /**
     * 日期时间格式化器
     */
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    /**
     * 过滤器核心逻辑：记录完整的请求响应日志
     * <p>
     * 执行流程：
     * 1. 检查是否启用日志记录
     * 2. 记录请求开始时间
     * 3. 提取请求信息（方法、路径、追踪 ID、请求体）
     * 4. 装饰响应对象，拦截响应体
     * 5. 执行过滤器链处理请求
     * 6. 响应完成后输出一条完整日志（包含所有信息）
     *
     * @param exchange 当前请求的 ServerWebExchange
     * @param chain    过滤器链
     * @return Mono<Void> 异步响应
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 检查是否启用日志记录
        if (!props.getLogging().isEnabled()) {
            return chain.filter(exchange);
        }

        // 2. 记录请求开始时间
        long startTime = System.currentTimeMillis();
        String startTimeStr = LocalDateTime.now().format(DATE_TIME_FORMATTER);

        // 3. 提取请求信息
        ServerHttpRequest request = exchange.getRequest();
        String method = request.getMethod().name();
        String path = request.getURI().getRawPath();
        String query = request.getURI().getQuery();
        String fullPath = query == null ? path : path + "?" + query;

        // 4. 提取或生成追踪 ID
        String traceId = Optional.ofNullable(request.getHeaders().getFirst(RequestConst.TRACE_ID))
                .orElseGet(() -> String.valueOf(System.nanoTime()));

        // 5. 获取缓存的请求体（入参）
        String requestBody = exchange.getAttribute(RequestBodyCacheFilter.CACHED_REQUEST_BODY_ATTR);
        String finalRequestBody = requestBody != null ? requestBody : "";

        // 6. 装饰响应对象，拦截响应体
        ServerHttpResponse originalResponse = exchange.getResponse();
        DataBufferFactory bufferFactory = originalResponse.bufferFactory();
        String finalTraceId = traceId;

        ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(originalResponse) {
            @NonNull
            @Override
            public Mono<Void> writeWith(@NonNull Publisher<? extends DataBuffer> body) {
                if (body instanceof Flux<? extends DataBuffer> fluxBody) {
                    return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                        // 合并所有 DataBuffer
                        DataBufferFactory dataBufferFactory = originalResponse.bufferFactory();
                        DataBuffer joinedBuffer = dataBufferFactory.join(dataBuffers);

                        // 读取响应体内容
                        byte[] content = new byte[joinedBuffer.readableByteCount()];
                        joinedBuffer.read(content);
                        DataBufferUtils.release(joinedBuffer);

                        // 转换为字符串（出参）
                        String responseBody = new String(content, StandardCharsets.UTF_8);

                        // 计算执行时间
                        long duration = System.currentTimeMillis() - startTime;

                        // 获取响应状态码
                        int statusCode = Optional.ofNullable(originalResponse.getStatusCode())
                                .map(HttpStatusCode::value)
                                .orElse(200);

                        // 输出一条完整日志（包含所有信息）
                        log.info("[Gateway] {} | {} {} -> {} ({}ms) traceId={} | 入参: {} | 出参: {}",
                                startTimeStr,
                                method,
                                fullPath,
                                statusCode,
                                duration,
                                finalTraceId,
                                truncateBody(finalRequestBody),
                                truncateBody(responseBody));

                        // 返回新的 DataBuffer
                        return bufferFactory.wrap(content);
                    }));
                }
                return super.writeWith(body);
            }

            @NonNull
            @Override
            public Mono<Void> writeAndFlushWith(@NonNull Publisher<? extends Publisher<? extends DataBuffer>> body) {
                return writeWith(Flux.from(body).flatMapSequential(p -> p));
            }
        };

        // 7. 使用装饰后的响应对象继续过滤器链
        return chain.filter(exchange.mutate().response(decoratedResponse).build());
    }

    /**
     * 截断响应体内容，避免日志过长
     *
     * @param body 响应体内容
     * @return 截断后的内容
     */
    private String truncateBody(String body) {
        if (body == null) {
            return "";
        }
        if (body.length() <= MAX_LOG_LENGTH) {
            return body;
        }
        return body.substring(0, MAX_LOG_LENGTH) + "... (truncated)";
    }

    /**
     * 过滤器执行顺序
     * <p>
     * 返回 -80，在认证、限流之后执行，确保记录所有通过认证和限流的请求
     * <p>
     * 执行顺序参考：
     * - CorsFilter: -200 (CORS 跨域)
     * - BlacklistFilter: -110 (黑名单检查)
     * - JwtAuthFilter: -100 (认证)
     * - RequestBodyCacheFilter: -90 (请求体缓存)
     * - RateLimitFilter: -88 (限流)
     * - RequestLoggingFilter: -80 (日志记录)
     * <p>
     * 这样可以确保：
     * - 只记录通过认证的请求
     * - 记录限流后的实际请求
     * - 日志中的耗时包含前面所有过滤器的处理时间
     *
     * @return 顺序值，越小越早执行
     */
    @Override
    public int getOrder() {
        // 在认证和限流之后执行
        return -80;
    }
}